
/*
 * phc -- the open source PHP compiler
 * See doc/license/README.license for licensing information
 *
 * Generate C code
 *
 * We define a virtual class "Pattern" which corresponds to a particular kind
 * of statement that we can generate code for. We inherit from Pattern to 
 * define the various statements we can translate, create instances of each
 * of these classes, and then for every statement in the input, cycle through
 * all the patterns to find one that matches, and then call "generate" on that
 * pattern.
 */

// TODO Variable_variables cannot be used to access superglobals. See warning
// in http://php.net/manual/en/language.variables.superglobals.php
// TODO: that is not true - add a test case.

// TODO:
//		magic methods are:
//			__construct
//			__destruct
//			__get
//			__set
//			__unset
//			__isset
//			__call
//			__toString
//			__serialize
//			__unserialize
//
//	So that means casts are pure.

#include <fstream>
#include <boost/format.hpp>

#include "lib/List.h"
#include "lib/Set.h"
#include "lib/Map.h"
#include "lib/escape.h"
#include "lib/demangle.h"
#include "process_ir/General.h"
#include "process_ir/XML_unparser.h"
#include "codegen/Generate_C_annotations.h"
#include "process_mir/MIR_to_AST.h"
#include "pass_manager/Pass_manager.h"

#include "Generate_C.h"
#include "parsing/MICG_parser.h"
#include "embed/embed.h"


//#define RTS(STR) buf << increment_stat (STR);
string init_stats () 
{
	if (args_info.rt_stats_flag)
		return "init_counters ();\n";
	else
		return "";
}

string finalize_stats ()
{
	if (args_info.rt_stats_flag)
		return "finalize_counters ();\n";
	else
		return "";
}

string increment_stat (string name)
{
	if (!args_info.rt_stats_flag)
		return "";

	stringstream ss;
	ss 
	<< "increment_counter (\""
	<< name << "\", "
	<< name.size () + 1 << ", "
	<< get_hash (s(name))
	<< ");\n"
	;

	return ss.str ();
}



string method_mod_flags(Method_mod* mod)
{
	stringstream flags;

	if(mod->is_public)
	{
		flags << "ZEND_ACC_PUBLIC";
		assert(!mod->is_protected);
		assert(!mod->is_private);
	}
	else if(mod->is_protected)
	{
		flags << "ZEND_ACC_PROTECTED";
		assert(!mod->is_public);
		assert(!mod->is_private);
	}
	else if(mod->is_private)
	{
		flags << "ZEND_ACC_PRIVATE";
		assert(!mod->is_public);
		assert(!mod->is_protected);
	}
	else
	{
		// No access modifiers specified; assume public
		flags << "ZEND_ACC_PUBLIC";
	}

	if(mod->is_static)   flags << " | ZEND_ACC_STATIC";
	if(mod->is_abstract) flags << " | ZEND_ACC_ABSTRACT";
	if(mod->is_final)    flags << " | ZEND_ACC_FINAL";

	return flags.str();
}

string attr_mod_flags(Attr_mod* mod)
{
	stringstream flags;

	if(mod->is_public)
	{
		flags << "ZEND_ACC_PUBLIC";
		assert(!mod->is_protected);
		assert(!mod->is_private);
	}
	else if(mod->is_protected)
	{
		flags << "ZEND_ACC_PROTECTED";
		assert(!mod->is_public);
		assert(!mod->is_private);
	}
	else if(mod->is_private)
	{
		flags << "ZEND_ACC_PRIVATE";
		assert(!mod->is_public);
		assert(!mod->is_protected);
	}
	else
	{
		// No access modifiers specified; assume public
		flags << "ZEND_ACC_PUBLIC";
	}

	if(mod->is_static) flags << " | ZEND_ACC_STATIC";
	// const is not an access modifier

	return flags.str();
}

void function_declaration_block(ostream& buf, Signature_list* methods, String* block_name)
{
	buf << "// ArgInfo structures (necessary to support compile time pass-by-reference)\n";
	foreach (Signature* s, *methods)
	{
		String* name = s->method_name->value;

		// TODO: pass by reference only works for PHP>5.1.0. Do we care?
		buf 
		<< "ZEND_BEGIN_ARG_INFO_EX(" << *block_name << "_" << *name << "_arg_info, 0, "
		<< (s->is_ref ? "1" : "0")
		<< ", 0)\n"
		;

		// TODO: deal with type hinting

		foreach (Formal_parameter* fp, *s->formal_parameters)
		{
			buf 
			<< "ZEND_ARG_INFO("
			<< (fp->is_ref ? "1" : "0")
			<< ", \"" << *fp->var->variable_name->value << "\")\n"; 
		}

		buf << "ZEND_END_ARG_INFO()\n\n";
	}

	buf 
		<< "static function_entry " << *block_name << "_functions[] = {\n"
		;

	foreach (Signature* s, *methods)
	{
		String* name = s->method_name->value;

		String* class_name = NULL;
		if(s->attrs->has("phc.codegen.class_name"))
			class_name = s->attrs->get_string("phc.codegen.class_name");

		if(class_name == NULL)
		{
			buf << "PHP_FE(" << *name << ", " << *block_name << "_" << *name << "_arg_info)\n";
		}
		else
		{
			// TODO: deal with visibility flags
			buf << "PHP_ME(" << *class_name << ", " << *name << ", " << *block_name << "_" << *name << "_arg_info, " << method_mod_flags(s->method_mod) << ")\n";
		}
	}

	buf << "{ NULL, NULL, NULL }\n";
	buf << "};\n";
}


// Label supported features
void phc_unsupported (Node* node, const char* feature)
{
	cerr << "Not yet supported in code generation: " << feature << endl;
	(new MIR_unparser (cerr, true))->unparse (node);
	cerr << endl;
	xml_unparse (node, cerr);


	//	exit (-1);
}


string read_file (String* filename)
{
	// For now, we simply include this.
	ifstream file;

	stringstream ss1, ss2;
	ss1 << "runtime/" << *filename;
	ss2 << DATADIR << "/phc/" << *filename;

	// Check the current directory first. This means we can change the file without recompiling or installing.
	file.open (ss1.str ().c_str ());

	// Check the installed directory.
	if (!file.is_open())
		file.open (ss2.str ().c_str ());

	if (!file.is_open ())
		phc_internal_error ("Expected file missing: %s (expected at %s or %s)",
			filename->c_str(),
			ss2.str().c_str(),
			ss1.str().c_str());

	stringstream ss;
	while (not file.eof ())
	{
		string str;
		getline (file, str);
		ss << str << endl;
	}

	file.close ();
	assert (file.is_open () == false);
	return ss.str ();
}

void include_file (ostream& out, String* filename)
{
	out << read_file (filename);
}


string cb_write_literal_directly_into_zval (Object_list* params)
{
	String* name = dyc<String> (params->front ());
	Literal* lit = dyc<Literal> (params->back());

	return write_literal_directly_into_zval (*name, lit);
}


string get_hash (VARIABLE_NAME* name)
{
	return get_hash (name->value);
}

string suffix (string str, string suffix)
{
	stringstream ss;
	ss << str << "_" << suffix;
	return ss.str ();
}

string prefix (string str, string prefix)
{
	stringstream ss;
	ss << prefix << "_" << str;
	return ss.str ();
}

string get_non_st_name (String* var_name)
{
	return prefix (*var_name, "local");
}

string get_non_st_name (VARIABLE_NAME* var_name)
{
	assert (var_name->attrs->is_true ("phc.codegen.st_entry_not_required"));
	return get_non_st_name (var_name->value);
}

// Declare and fetch a zval* containing the value for RVALUE. The value can be
// changed, but the symbol-table entry cannot be affected through this.
string read_rvalue (string zvp, Rvalue* rvalue)
{
	stringstream ss;
	if (isa<Literal> (rvalue))
	{
		Literal* lit = dyc<Literal> (rvalue);
		if (args_info.optimize_given)
		{
			ss 
				<< "zvalT " << zvp << " = "
				<<	*lit->attrs->get_string ("phc.codegen.pool_name")
				<< ";\n";
		}
		else
		{
			ss
				<< "zvalTV " << zvp << "_lit_tmp;\n"
				<< "INIT_ZVAL (" << zvp << "_lit_tmp);\n"
				<< "zvalT " << zvp << " = " << zvp << "_lit_tmp;\n"
				<< write_literal_directly_into_zval (zvp, lit)
				;
		}
		return ss.str();
	}

	VARIABLE_NAME* var_name = dyc<VARIABLE_NAME> (rvalue);
	if (var_name->attrs->is_true ("phc.codegen.st_entry_not_required"))
	{
		string name = get_non_st_name (var_name);
		ss
		<< "zvalT " << zvp << ";\n"
		<< "if (" << name << " == NULL)\n"
		<< "{\n"
		<<		zvp << " = EG (uninitialized_zval_ptr);\n"
		<< "}\n"
		<< "else\n"
		<< "{\n"
		<<		zvp << " = " << name << ";\n"
		<< "}\n"
		;
	}
	else
	{
		String* name = var_name->value;
		ss	
		<< "zvalT " << zvp << " = read_var (" 
		<<								get_scope (LOCAL) << ", "
		<<								"\"" << *name << "\", "
		<<								name->size () + 1  << ", "
		<<								get_hash (name) << " TSRMLS_CC);\n"
		;
	}
	return ss.str ();
}

// TODO: This should be integrated into each function. In particular, we want
// to remove the slowness of adding the uninitialized_zval_ptr, only to remove
// it a second later.

// Declare and fetch a zval** into ZVP, which is the symbol-table entry for
// VAR_NAME. This zval** can be over-written, which will change the
// symbol-table entry.
string get_st_entry (Scope scope, string zvp, VARIABLE_NAME* var_name)
{
	stringstream ss;
	if (scope == LOCAL && var_name->attrs->is_true ("phc.codegen.st_entry_not_required"))
	{
		string name = get_non_st_name (var_name);
		ss
		<< "if (" << name << " == NULL)\n"
		<< "{\n"
		<<		name << " = EG (uninitialized_zval_ptr);\n"
		<<		name << "->refcount++;\n"
		<< "}\n"
		<<	"zvalTT " << zvp << " = " << name << ";\n";
	}
	else
	{
		String* name = var_name->value;
		ss
		<< "zvalTT " << zvp << "= get_st_entry (" 
		<<									get_scope (scope) << ", "
		<<									"\"" << *name << "\", "
		<<									name->size () + 1  << ", "
		<<									get_hash (name) << " TSRMLS_CC);\n";
	}
	return ss.str();
}


/* Generate code to read the variable named in VAR to the zval* ZVP */
string read_var (string zvp, VARIABLE_NAME* var_name)
{
	// the same as read_rvalue, but doesnt declare
	stringstream ss;
	string name = suffix (zvp, "var");

	ss
	<< "// Read normal variable\n"
	<< read_rvalue (name, var_name)
	<< zvp << " = &" << name << ";\n"; 

	return ss.str();
}

/*
 * Find the variable in target_scope whose name is given by var_var in var_scope
 * and store the result in zvp
 */
string get_var_var (Scope scope, string zvp, string index)
{
	stringstream ss;
	ss
	<< "// Read variable variable\n"
	<< "zvalTT " << zvp << " = get_var_var (" 
	<<					get_scope (scope) << ", "
	<<					index << " "
	<<					"TSRMLS_CC);\n"
	;
	return ss.str();
}

/*
 * Like get_var_var, but do not add the variable to the scope
 * if not already there
 */
string read_var_var (string zvp, string index)
{
	stringstream ss;
	ss
	<< "// Read variable variable\n"
	<< zvp << " = read_var_var (" 
	<<					get_scope (LOCAL) << ", "
	<<					index << " "
	<<					" TSRMLS_CC);\n"
	;
	return ss.str();
}

string get_scope (Scope scope)
{
	if(scope == LOCAL)
		return "EG(active_symbol_table)";
	else
		return "&EG(symbol_table)";
}

string get_hash (String* name)
{
	// the "u" at the end of the constant makes it unsigned, which
	// stops gcc warning us about it.
	stringstream ss;
	ss << PHP::get_hash (name) << "u";
	return ss.str ();
}

/*
 * Callbacks
 */
string cb_get_hash (Object_list* params)
{
	Object* name = params->front ();
	assert (isa<Identifier> (name) || isa<String> (name));
	return (get_hash (MICG_gen::convert_to_string (name)));
}

string cb_get_length (Object_list* params)
{
	Object* name = params->front ();
	assert (isa<Identifier> (name) || isa<String> (name));
	return lexical_cast<string> (MICG_gen::convert_to_string (name)->size ());
}

string cb_is_literal (Object_list* params)
{
	Object* obj = params->front ();
	assert (isa<Rvalue> (obj));

	if (isa<MIR::Literal> (obj))
		return MICG_TRUE;
	else
		return MICG_FALSE;
}



string
write_literal_directly_into_zval (string var, Literal* lit)
{
	stringstream ss;
	if (INT* value = dynamic_cast<INT*> (lit))
	{
		ss << "ZVAL_LONG (" << var << ", " << value->value << ");\n";
	}
	else if (REAL* value = dynamic_cast<REAL*> (lit))
	{
		ss
		<< "{\n"
		<< "	unsigned char val[] = {\n"
		;

		// Construct the value a byte at a time from our representation in memory.
		unsigned char* values_bytes = (unsigned char*)(&value->value);
		for (unsigned int i = 0; i < sizeof (double); i++)
		{
			ss << (unsigned int)(values_bytes[i]) << ", ";
		}
		ss
		<< "};\n"
		<< "ZVAL_DOUBLE (" << var << ", *(double*)(val));\n"
		<< "}\n"
		;
	}
	else if (STRING* value = dynamic_cast<STRING*> (lit))
	{
		ss
		<< "ZVAL_STRINGL(" << var << ", " 
		<<		"\"" << *escape_C_dq (value->value) << "\", "
		<<		value->value->length() << ", 1);\n";
	}
	else if (/* NIL* value = */ dynamic_cast<NIL*> (lit))
	{
		ss << "ZVAL_NULL (" << var << ");\n";
	}
	else if (BOOL* value = dynamic_cast<BOOL*> (lit))
	{
		ss
		<< "ZVAL_BOOL (" << var << ", " 
		<<		(value->value ? 1 : 0) << ");\n"
		;
	}

	return ss.str();
}


void
init_function_record (ostream& buf, string name, string& fci_name, string& fcic_name, Node* node)
{
	// Its not necessarily a good idea to initialize at the start, since we
	// still have to check if its initialized at call-time (it may have been
	// created in the meantime.
	buf
	<< "initialize_function_call (" 
	<<			"&" << fci_name << ", "
	<<			"&" << fcic_name << ", "
	<<			"\"" << name << "\", "
	<<			"\"" << *node->get_filename () << "\", " 
	<<			node->get_line_number ()
	<<			" TSRMLS_CC);\n"
	;
}

void
init_method_record (ostream& buf, string obj, string name, string& fci_name, string& fcic_name, string ht, Node* node)
{
	buf
	<< "zvalTMethod " << ht << " = " << "initialize_method_call (" 
	<<	fci_name << ", "
	<<	fcic_name << ", "
	<<      obj << ", "
	<<			"\"" << name << "\", "
	<<			"\"" << *node->get_filename () << "\", " 
	<<			node->get_line_number ()
	<<			" TSRMLS_CC);\n"
	;
}
